package tools;

import interfaces.Collidable;
import java.util.HashSet;
import java.util.Set;

public class CollisionGrid {
    public static class Cell {
        public final Set<Collidable> COLLIDABLES = new HashSet<Collidable>();
        public boolean floor = true;
        public boolean wall = false;
        public boolean solid = false;
    }

    private final Cell[][] GRID;
    private final int ROWS;
    private final int COLS;

    public CollisionGrid(int rows, int cols) {
        ROWS = rows;
        COLS = cols;
        GRID = new Cell[ROWS][COLS];
        for (int row = 0; row < ROWS; row++) {
            for (int col = 0; col < COLS; col++) {
                GRID[row][col] = new Cell();
            }
        }
    }

    public int getRows() {
        return ROWS;
    }

    public int getCols() {
        return COLS;
    }

    public void set(int row, int col, boolean floor, boolean wall, boolean solid) {
        GRID[row][col].floor = floor;
        GRID[row][col].wall = wall;
        GRID[row][col].solid = solid;
    }

    public boolean hasSolid(Bounds bounds) {
        try {
            int maxRow = bounds.getMaxRow();
            int maxCol = bounds.getMaxCol();
            for (int row = bounds.getMinRow(); row < maxRow; row++) {
                for (int col = bounds.getMinCol(); col < maxCol; col++) {
                    if (GRID[row][col].solid) {
                        return true;
                    }
                }
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            return true;
        }
        return false;
    }

    public boolean isSolid(int row, int col) {
        try {
            return GRID[row][col].solid;
        } catch (ArrayIndexOutOfBoundsException e) {
            return true;
        }
    }

    public boolean isFloor(int row, int col) {
        try {
            return GRID[row][col].floor;
        } catch (ArrayIndexOutOfBoundsException e) {
            return true;
        }
    }

    public boolean isWall(int row, int col) {
        try {
            return GRID[row][col].wall;
        } catch (ArrayIndexOutOfBoundsException e) {
            return true;
        }
    }

    public void createFromString(String str) {
        byte[] lev = Crypt.decode(str);
        lev = Crypt.decrypt(lev, "Random level key".getBytes(), 32);
        for (int i = 0; i < lev.length; i++) {
            for (int b = 0; b < 8 && i * 8 + b < COLS * ROWS; b++) {
                if ((lev[i] >> b & 1) == 1) {
                    GRID[(i * 8 + b) / COLS][(i * 8 + b) % COLS].wall = true;
                    GRID[(i * 8 + b) / COLS][(i * 8 + b) % COLS].solid = true;
                } else {
                    GRID[(i * 8 + b) / COLS][(i * 8 + b) % COLS].wall = false;
                    GRID[(i * 8 + b) / COLS][(i * 8 + b) % COLS].solid = false;
                }
            }
        }
    }

    public void addCollidable(final Collidable collidable) {
        Bounds bounds = collidable.getBounds();
        int maxRow = bounds.getMaxRow();
        int maxCol = bounds.getMaxCol();
        for (int row = bounds.getMinRow(); row < maxRow; row++) {
            for (int col = bounds.getMinCol(); col < maxCol; col++) {
                GRID[row][col].COLLIDABLES.add(collidable);
            }
        }
    }

    /**
     * Removes references to the given collidable.
     * 
     * @param collidable the collidable
     */
    public final void removeCollidable(final Collidable collidable) {
        Bounds bounds = collidable.getBounds();
        int maxRow = bounds.getMaxRow();
        int maxCol = bounds.getMaxCol();
        for (int row = bounds.getMinRow(); row < maxRow; row++) {
            for (int col = bounds.getMinCol(); col < maxCol; col++) {
                GRID[row][col].COLLIDABLES.remove(collidable);
            }
        }
    }

    /**
     * @param bounds the bounding box
     * @return the set of collidables which intersect the given
     *         bounding box
     */
    public final Set<Collidable> getCollidables(final Bounds bounds) {
        Set<Collidable> collidables = new HashSet<Collidable>();
        int maxRow = bounds.getMaxRow();
        int maxCol = bounds.getMaxCol();
        for (int row = bounds.getMinRow(); row < maxRow; row++) {
            for (int col = bounds.getMinCol(); col < maxCol; col++) {
                for (Collidable collidable : new HashSet<Collidable>(
                        GRID[row][col].COLLIDABLES)) {
                    if (bounds.intersects(collidable.getBounds())) {
                        collidables.add(collidable);
                    }
                }
            }
        }
        return collidables;
    }

    /** Clears all references held by this object. */
    public final void clear() {
        for (int row = 0; row < ROWS; row++) {
            for (int col = 0; col < COLS; col++) {
                GRID[row][col].COLLIDABLES.clear();
                GRID[row][col].floor = true;
                GRID[row][col].wall = false;
                GRID[row][col].solid = false;
            }
        }
    }
}
